#ifndef Chromosome_header_file
#define Chromosome_header_file

#include "RandomNumberGenerator.h"
#include <iostream>
#include <cmath>
#include <fstream>
#include <iomanip>

// GAMA(k) of MA Module
double* testData = nullptr;
double testDataTotal = 0;

// Trans data
double* testOriginData = nullptr;
double testOriginTotal = 0;

class Chromosome {

public:

    static int length; // length = q + 1

    static Chromosome* generateRandomChromosome() {
        auto chromosome = new Chromosome();
        auto randomNumberGenerator = RandomNumberGenerator::getRandomNumberGenerator();
        for (int i = 0; i < length; i++) {
            chromosome->setGene(i, randomNumberGenerator->getRandomNumber(-1.0, 1.0));
        }
        return chromosome;
    }

    double getFitness() {
        if (fitness >= 0) {
            return fitness;
        }
        // 先估计噪声的方差
        long int sequenceLength = lround(testDataTotal);
        auto noise = new double[sequenceLength];
        double sum = 0;
        double avgNoise = 0;
        double varNoise = 0;
        for (long int i = 0; i < sequenceLength; i++) {
            noise[i] = testOriginData[i];
            for (long int j = 0; j < length; j++) {
                if (i - j - 1 < 0) {
                    break;
                }
                noise[i] -= genes[j] * noise[i - j - 1];
            }
            sum += noise[i];
        }
        avgNoise = sum / (double)sequenceLength;
        for (long int i = 0; i < sequenceLength; i++) {
            varNoise += (noise[i] - avgNoise) * (noise[i] - avgNoise) / (double)sequenceLength;
        }
        delete[] noise;
        // 利用上面估计的方差估计GAMA函数并和实测值对比
        double gama = 0;
        double feia = 0;
        double feib = 0;
        double error = 0;
        for (long int i = 0; i <= length; i++) {
            sum = 0;
            for (long int j = 0; j < length - i + 1; j++) {
                feia = (j == 0) ? 1.0 : genes[j - 1];
                feib = (j + i == 0) ? 1.0 : genes[j + i - 1];
                sum += feia * feib;
            }
            gama = varNoise * sum;
            error += (gama - testData[i]) * (gama - testData[i]) / (double)(length + 1);
        }
        fitness = 1.0 / (error + 1.0);
        return fitness;
    }

    Chromosome* cross(Chromosome* anotherChromosome) {
        auto newChromosome = new Chromosome();
        auto randomNumberGenerator = RandomNumberGenerator::getRandomNumberGenerator();
        for (int i = 0; i < length; i++) {
            if (randomNumberGenerator->getRandomNumber(-1.0, 1.0) > 0.0) {
                newChromosome->setGene(i, genes[i]);
            } else {
                newChromosome->setGene(i, anotherChromosome->getGene(i));
            }
        }
        return newChromosome;
    }

    void varies(double p) {
        auto randomNumberGenerator = RandomNumberGenerator::getRandomNumberGenerator();
        for (int i = 0; i < length; i++) {
            if (randomNumberGenerator->getRandomNumber(0.0, 1.0) <= p) {
                genes[i] += randomNumberGenerator->getRandomNumber(-0.1, 0.1);
            }
        }
    }

    ~Chromosome() {
        delete[] genes;
    }

    double getGene(int i) {
        return genes[i];
    }

    void setGene(int i, double value) {
        genes[i] = value;
    }

    void dump() {
        using namespace std;
        cout << "MA(" << (length - 1) << "):" << endl;
        for (int i = 0; i < length; i++) {
            cout << "a(" << (i + 1) << ")=" << genes[i] << endl;
        }
    }

    void createRemanentErrorFile(std::string& fileName) {
        using namespace std;
        ofstream errorFile(fileName);
        if (!errorFile.is_open()) {
            cout << "Warning: Open \"" << fileName << "\" fail." << endl;
            return;
        }
        errorFile << fixed << setprecision(9);
        long int sequenceLength = lround(testDataTotal);
        auto noise = new double[sequenceLength];
        for (long int i = 0; i < sequenceLength; i++) {
            noise[i] = testOriginData[i];
            for (long int j = 0; j < length; j++) {
                if (i - j - 1 < 0) {
                    break;
                }
                noise[i] -= genes[j] * noise[i - j - 1];
            }
            errorFile << noise[i] << endl;
        }
        delete[] noise;
    }

protected:

    Chromosome() {
        genes = new double[length];
    }

private:

    // genes[0] is var, genes[1 to length - 1] is ma-module argument
    double* genes;

    double fitness = -1.0;

};

int Chromosome::length = 0;

#endif
